﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: ObjectPool.cs
//
//  Description: A simple pool for objects.
// 
//--------------------------------------------------------------------------

using System;
using System.Threading;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using Chili.Opf3.Mapping;

namespace Chili.Opf3
{
	/// <summary>
	/// A simple object pool for objects. Is used to populate faster
	/// big collections and to create faster large amounts of objects.
	/// </summary>
	internal sealed class ObjectPool
	{
		#region Cache

		/// <summary>
		/// Class that is a cache for one type of object.
		/// </summary>
		private sealed class Cache 
		{
			private ObjectPool _pool;
			private Queue _cache = Queue.Synchronized(new Queue());
			private bool _populating;
			private Type _objectType;
			private int _size;
			private object[] _constructorParameter;

			/// <summary>
			/// Constructor.
			/// </summary>
			/// <param name="pool">The instance of the ObjectPool this instance is part of.</param>
			/// <param name="objectType">Type of the objects in the cache.</param>
			/// <param name="constructorParameter">Parameter for the constructor.</param>
			/// <param name="size">Size of the cache.</param>
			public Cache(ObjectPool pool, Type objectType, object[] constructorParameter, int size)
			{
				if (pool == null)
					throw new ArgumentNullException("pool");
				if (objectType == null)
					throw new ArgumentNullException("objectType");

				_pool = pool;
				_objectType = objectType;
				_constructorParameter = constructorParameter;
				_size = size;
			}

			/// <summary>
			/// Returns the first object in the cache. Removes it after
			/// having it returned.
			/// </summary>
			public object GetObject()
			{
				if (_size <= 0)
					return _pool.CreateObject(_objectType, _constructorParameter);

				// Start a refill thread if the cache of the requested objects is below 10 %.
				if (_cache.Count <= (_size * 0.1))
					PopulateCache();

				// If the cache contains one or more elements return an element of the cache. If
				// the cache is empty create a new object on the fly and return that object.
				if (_cache.Count > 0)
					return _cache.Dequeue();
				else
					return _pool.CreateObject(_objectType, _constructorParameter);
			}

			/// <summary>
			/// Starts the refill process of the cache.
			/// </summary>
			private void PopulateCache()
			{
				lock (this)
				{
					if (_populating)
						return;
					else
						_populating = true;
				}

				Thread thread = new Thread(new ThreadStart(RefillCache));
				thread.IsBackground = true;
				thread.Name = "ObjectPool Refill";
				thread.Start();
			}

			/// <summary>
			/// Refills the cache.
			/// </summary>
			private void RefillCache()
			{
				Tracing.Current.TraceVerbose("Refilling the ObjectPool containing cached objects. Object type: " + _objectType.ToString());
				// Add objects until the cache is fully refilled.
				while (_cache.Count < _size)
					_cache.Enqueue(_pool.CreateObject(_objectType, _constructorParameter));

				_populating = false;
			}

			/// <summary>
			/// Returns the object type of the cache.
			/// </summary>
			public Type ObjectType
			{
				get { return _objectType; }
			}
		}

		#endregion

		private static ObjectPool _current = new ObjectPool();
		// Dictionary that contains the caches of objects.
		private IDictionary<Type, Cache> _caches = new Dictionary<Type, Cache>();
		private Cache _lastCache;
		private IPersistentFactory _factory = new DynamicConstructorPersistentFactory();

		/// <summary>
		/// Creates a new instance of the <see cref="ObjectPool">ObjectPool Class</see>.
		/// </summary>
		/// <remarks>This constructor is private to avoid creating more the one pool.</remarks>
		private ObjectPool()
		{
		}

		/// <summary>
		/// Returns the instance of the object pool.
		/// </summary>
		public static ObjectPool Current
		{
			get
			{
				return _current;
			}
		}

		/// <summary>
		/// Adds an object type to the cache. 
		/// </summary>
		/// <param name="type">Type of object that is added to the cache.</param>
		/// <param name="constructorParameter">Constructor parameter for the object.</param>
		/// <exception cref="ArgumentNullException">Type is null.</exception>
		private Cache AddType(Type type, object[] constructorParameter)
		{
			PersistentAttribute attribute = TypeMapping.GetTypeMapping(type).PersistentAttribute;
			// Add a new empty cache.
            Cache cache = new Cache(this, type, constructorParameter, attribute.PoolSize);
			_caches.Add(type, cache);

			return cache;
		}

		/// <summary>
		/// Creates a new instance of the given object type.
		/// </summary>
		/// <param name="type">The type of object whose instance is created.</param>
		/// <param name="parameters">A list of constructor parameters</param>
		internal object CreateObject(Type type, object[] parameters)
		{
			// Check if a factory is available.
			if (_factory != null)
			{
				// Create an instance using the factory.
				object instance = _factory.CreateInstance(type, parameters);
				if (instance != null)
					return instance;
			}

			// Fall back to the reflection method, if no factory is available or the factory does not create an instance of an object.
			return Activator.CreateInstance(type, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, parameters, null);
		}

		/// <summary>
		/// Returns one object of the given type.
		/// </summary>
		/// <param name="type">Type of the required object.</param>
		/// <exception cref="ArgumentNullException">Type is null.</exception>
		public object GetObject(Type type)
		{
			lock (this)
			{
				if (_lastCache != null && _lastCache.ObjectType == type)
					return _lastCache.GetObject();

				Cache cache = null;
				if (!_caches.TryGetValue(type, out cache))
					cache = AddType(type, null);

				// Set the current cache as last cache that has been fetched.
				_lastCache = cache;
				return cache.GetObject();
			}
		}

		/// <summary>
		/// Specifies a class implementing the <see cref="IPersistentFactory">IPersistentFactory</see> used to create
		/// instances of the persistent object.
		/// </summary>
		internal IPersistentFactory PersistentFactory
		{
			get { return _factory; }
			set { _factory = value; }
		}
	}
}
